﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PerCederberg.Grammatica.Runtime;

namespace SafeScript.MetaModel
{
    public class Scope
    {
        /// <summary>
        /// The objects stored here can be namespaces, classes, or member variables, member functions and so on
        /// </summary>
        public Dictionary<string, object> ScopeElements = new Dictionary<string, object>();
        public Scope ParentScope;

        public virtual object GetScopeElement(string name)
        {
            // Look at the elements in the scope
            object obj = null;
            if ( this.ScopeElements.TryGetValue(name, out obj) )
                return obj;

            return null;
        }

        public virtual ScopeResolution GetScopeElement(ScopedIdentifier name)
        {
            return this.GetScopeElement(name, 0);
        }

        public ScopeResolution GetScopeElement(ScopedIdentifier name, int startIndex)
        {
            ScopeResolution res = new ScopeResolution();
            Scope scope = this;
            for (int i = startIndex; i < name.Count; ++i)
            {
                object obj = scope.GetScopeElement(name[i]);
                // Name could not be found?
                if (obj == null)
                {
                    // Nothing could be found -> null
                    if (i == 0)
                        return null;
                    // Some parts could be resolved but not all
                    return res;
                }
                res.Add(new ScopeResolutionItem() { Name = name[i], Element = obj });
                scope = obj as Scope;
                if (scope == null && i + 1 < name.Count )
                    // Some parts could be resolved but not all
                    return res;
            }

            // All parts could be resolved
            res.IsResolved = true;
            return res;
        }
    }

    public class ScopeResolutionItem
    {
        public string Name;
        public object Element;
    }

    public class ScopeResolution : List<ScopeResolutionItem>
    {
        public bool IsResolved = false;

        public object GetFinalElement()
        {
            if (!this.IsResolved)
                return null;
            if (this.Count == 0)
                return null;
            return this[this.Count - 1].Element;
        }
    }

    public class ScopedIdentifier : List<string>
    {
        public ScopedIdentifier(Node node)
        {
            System.Diagnostics.Debug.Assert(node.Id == (int)SafeScriptConstants.SCOPED_IDENTIFIER);
            while (node != null)
            {
                this.Add( (node[0] as Token).Image );
                node = Helper.GetNode(node, SafeScriptConstants.SCOPED_IDENTIFIER);
            }
        }

        public ScopedIdentifier(string name)
        {
            this.Add(name);
        }

        public ScopedIdentifier(string[] name)
        {
            this.AddRange(name);
        }

        public override string ToString()
        {
            return string.Join(".", this.ToArray());
        }
    }

    class TypeResolver
    {
        public Node TypeDefNode;
        public Namespace Context;
        public Using Use;

        public ITypeDef ResolveType(ErrorReport report)
        {
            return this.Context.ParseTypeDef(this.TypeDefNode, this.Use, report);
        }
    }

    class ScopeResolver
    {
        public Node Location;
        public ScopedIdentifier Ident;
        public Namespace Context;
        // public Using Use;

        public object ResolveScope()
        {
            return FindScope(this.Context, this.Ident, null);
        }

        public static Scope FindScope(Namespace context, ScopedIdentifier ident, Using use)
        {
            object obj = FindObject(context, ident[0], use, true);
            if (obj == null || !(obj is Scope))
                return null;
            if (ident.Count == 1)
                return obj as Scope;
            ScopeResolution res = (obj as Scope).GetScopeElement(ident, 1);
            if (res == null)
                return null;
            return res.GetFinalElement() as Scope;
        }


        /// <summary>
        /// Finds a namespace, class, or interface by using a scoped name.
        /// </summary>
        /// <param name="context">The scope in which the source originates.</param>
        /// <param name="ident">The scoped name to search for.</param>
        /// <param name="use">A set of using directives</param>
        /// <returns></returns>
        //public static Scope FindScope(Scope context, ScopedIdentifier ident, Using use)
        //{
        //    // Check local names, especially template parameter types
        //    if (use != null)
        //    {
        //        for (int i = use.Stack.Count - 1; i >= 0; --i)
        //        {
        //            Dictionary<string, object> dict = use.Stack.ElementAt(i);
        //            object obj = null;
        //            if (dict.TryGetValue(ident[0], out obj))
        //                if ( obj is Scope )
        //                    return obj as Scope;
        //        }
        //    }

        //    Scope scope = context;
        //    while (scope != null)
        //    {
        //        // Try the namespace
        //        ScopeResolution res = scope.GetScopeElement(ident);
        //        // Nothing found in the initial context? -> look at the imported and alias namespaces
        //        if (res == null && scope == context)
        //        {
        //            while (use != null)
        //            {
        //                // Try the alias namespaces
        //                Namespace alias = null;
        //                if (use.Alias.TryGetValue(ident[0], out alias))
        //                {
        //                    res = alias.GetScopeElement(ident, 1);
        //                    if (res == null || !res.IsResolved)
        //                        return null;
        //                    return res.GetFinalElement() as Scope;
        //                }
        //                // Try imported namespaces
        //                foreach (Namespace u in use.Imported)
        //                {
        //                    res = u.GetScopeElement(ident);
        //                    if (res != null)
        //                    {
        //                        if (res.IsResolved)
        //                            return res.GetFinalElement() as Scope;
        //                        return null;
        //                    }
        //                }
        //                use = use.ParentUsing;
        //            }
        //        }
        //        if (res == null)
        //        {
        //            scope = scope.ParentScope;
        //            continue;
        //        }
        //        if (!res.IsResolved)
        //            return null;
        //        return res.GetFinalElement() as Scope;
        //    }
        //    return null;
        //}

        /// <summary>
        /// Searches an object by its unscoped name. This happens inside expressions when
        /// you use some identifier. The result of the search can be a namespace/class/interface,
        /// class member or variable.
        /// </summary>
        /// <param name="context">The namespace in which the search originates.</param>
        /// <param name="ident"></param>
        /// <param name="use">May be null</param>
        /// <param name="checkParentScope">If true, the parent namespaces will be checked, too.
        /// This property is set to false when searching through base classes recursively.</param>
        /// <returns></returns>
        public static object FindObject(Namespace context, string ident, Using use, bool checkParentScope)
        {
            // Check local names (local variables, function parameters, ...) first
            if (use != null)
            {
                for (int i = 0; i < use.Stack.Count; ++i)
                {
                    Dictionary<string, object> dict = use.Stack.ElementAt(i);
                    object obj = null;
                    if (dict.TryGetValue(ident, out obj))
                        return obj;
                }
            }

            Scope scope = context;
            while (scope != null)
            {
                // Try the namespace
                object res = null;
                if (scope is AbstractType)
                {
                    // If the context is a type (class/interface), we must check the base types, too.
                    res = (scope as AbstractType).GetFlatScopeElement(ident);
                    if (res != null)
                        return res;
                }
                else
                {
                    // The context is a namespace
                    res = scope.GetScopeElement(ident);
                    if (res != null)
                        return res;
                }

                // Nothing found in the initial context? -> look at the imported and alias namespaces
                if (use != null)
                {
                    // Try the alias namespaces
                    Namespace alias = null;
                    if (use.Alias.TryGetValue(ident, out alias))
                        return alias;
                    // Try imported namespaces
                    foreach (Namespace u in use.Imported)
                    {
                        res = u.GetScopeElement(ident);
                        if (res != null)
                            return res;
                    }
                }
                scope = scope.ParentScope;
                if (use != null)
                    use = use.ParentUsing;
            }
            return null;
        }    
    }
}
